package xian.woniuxy.c_jdbcrealm;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;

/**
 * @author gao
 * @time 2021/12/23 14:37:09
 */
public class App {
    public static void main(String[] args) {
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // ====================================================================
        // 无论这两个线之间将来写多少行代码，那都是一个意思：制作Realm对象！
        // 我们要使用的是JdbcRealm，那就是要操作数据库，JdbcRealm会从数据库中获取账户密码，
        // 来与用户提交的账户密码比较。所以我们必须告诉JdbcRealm，数据库在哪里，这就需要配置
        // 数据源了：
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&serverTimezone=Asia/Shanghai");
        ds.setUsername("root");
        ds.setPassword("123");

        JdbcRealm realm = new JdbcRealm();
        realm.setDataSource(ds);

        // 设置加密配置，要提供加密算法、盐、迭代次数
        HashedCredentialsMatcher hcm = new HashedCredentialsMatcher("MD5");
        hcm.setHashIterations(1024);
        // 还少个盐，这个盐是通过执行认证的sql语句来查出来，也就是说，盐是存放在数据库里的，要从库里查出来
        // 还要告诉realm，认证查询sql，查询盐了，告诉realm，盐是sql里面查出来的
        realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
        realm.setCredentialsMatcher(hcm);



        // 还要告诉JdbcRealm，如何查找账户和密码，这必须使用sql语句，毕竟是连接数据库的
        // 当我们要认证的时候，JdbcRealm就会调用这个sql语句！
        // 如果认证时，执行的这个sql语句没有查询出任何一行，则说明账户不正确，
        realm.setAuthenticationQuery("SELECT PASSWORD,salt FROM users WHERE username = ?");
        // 当我们要判断一个主体是否有某个角色的时候（hasRole)，JdbcRealm就会调用以下配置的sql语句
        realm.setUserRolesQuery("\n" +
                "SELECT rname FROM users_roles ur JOIN users u\n" +
                "ON ur.uid = u.uid\n" +
                "JOIN roles r\n" +
                "ON ur.rid = r.rid\n" +
                "WHERE username = ?");
        // 当我们要判断一个主体是否有某个权限的时候（isPermitted），JdbcRealm就会调用以下配置的sql语句
        realm.setPermissionsQuery("SELECT pname FROM roles_permissions rp JOIN roles r\n" +
                "ON rp.rid = r.rid\n" +
                "JOIN permissions p\n" +
                "ON rp.pid = p.pid\n" +
                "WHERE rname = ?");

        // 务必做以下配置，这样shiro才能在我们要判断某一个用户是否有某个权限的时候，
        // 先查询出用户的角色，再拿着角色查询用户的权限，进而在与isPremitted()方法
        // 中的参数比较！
        realm.setPermissionsLookupEnabled(true);



        // ====================================================================
        securityManager.setRealm(realm);
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();
        // 用户从前端歘来的账户和密码
        String username = "foo";
        String password = "123";
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            // 当我们调用以下方法的时候，JdbcRealm就会调用：
            // SELECT PASSWORD FROM users WHERE username = ?  <-- token.username
            subject.login(token);
            System.out.println("认证成功");
        } catch (Exception e) {
            System.out.println("认证失败");
            e.printStackTrace();
        }

        System.out.println("是否通过认证：" + subject.isAuthenticated());

        // 认证之后就有身份了：bar
        System.out.println("身份：" + subject.getPrincipal());

        /*
         当我们调用hasRole方法时，就会执行以下sql：
            SELECT rname FROM users_roles ur JOIN users u
            ON ur.uid = u.uid
            JOIN roles r
            ON ur.rid = r.rid
            WHERE username = ?    <-- subject在认证之后所获取的身份，身份就是账户，就是bar
        */

        System.out.println("admin角色：" + subject.hasRole("admin"));
        System.out.println("guest角色：" + subject.hasRole("guest"));

        /*
            当我们调用isPermitted方法时，就会执行以下sql

                先查询用户角色
                SELECT rname FROM users_roles ur JOIN users u
                ON ur.uid = u.uid
                JOIN roles r
                ON ur.rid = r.rid
                WHERE username = ?    <-- subject在认证之后所获取的身份，身份就是账户，就是bar

                再拿着用户的角色，去查询用户的权限，如果用户有多个角色，
                则会把用户的多个角色的权限汇总在一起再判断
                SELECT pname FROM roles_permissions rp JOIN roles r
                ON rp.rid = r.rid
                JOIN permissions p
                ON rp.pid = p.pid
                WHERE rname = ?     <-- 这里的问号，就是上一个sql语句查询出的角色，如果有多个角色，这个sql就执行多次
         */
        System.out.println("user:save " + subject.isPermitted("user:save"));
        System.out.println("user:delete " + subject.isPermitted("user:delete"));
        System.out.println("user:update " + subject.isPermitted("user:update"));
        System.out.println("user:find " + subject.isPermitted("user:find"));

    }
}
